module(..., package.seeall)

require("lfs")

SimpleVersium = {}

INDEX_TEMPLATE=[[add_version{
 version   = %s,
 timestamp = %s,
 author    = %s,
 comment   = %s,
}
]]

----------------------------------------------------------------------------
-- An auxiliary function for opening files "safely" (i.e., with error
-- messages.)
-- 
-- @param path       the file path.
-- @param mode       the access mode ("r", "w", "a", etc.)
-- @return           a file handle
----------------------------------------------------------------------------

local function _open_file(path, mode, strict)
   local f = io.open(path, mode)
   if (not f) and strict then
      error("\nFile Versium: Can't open file:" .. path .. 
	    ", mode = ".. (mode or "r"))
   end
   return f
end

----------------------------------------------------------------------------
-- An auxiliary function for writing to a file.  Eventually this will
-- do atomic write.
-- 
-- @param path       the file path.
-- @param data       data to be written
-- @return           nothing
----------------------------------------------------------------------------

local function _write_file(path, data)
   local f = _open_file(path, "w", true)
   f:write(data)
   f:close()
end


----------------------------------------------------------------------------
-- An auxiliary function for reading the content of a file.
-- 
-- @param path       the file path.
-- @return           a string
----------------------------------------------------------------------------

local function _read_file(path)
   local f = _open_file(path, "r", true)
   local data = f:read("*all")
   f:close()
   return data
end

----------------------------------------------------------------------------
-- An auxiliary function for reading the content of a file without
-- complaining if it doesn't exist.
-- 
-- @param path       the file path.
-- @return           a string
----------------------------------------------------------------------------

local function _read_file_if_exists(path)
   local f = _open_file(path, "r", false)
   if f then
      local data = f:read("*all")
      f:close()
      return data
   else
      return ""
   end
end

----------------------------------------------------------------------------
-- Instantiates a new simple versium object.
-- 
-- @param impl       the implementation module.
-- @param params     the parameters to pass to the implementation.
-- @return           a new versium object.
----------------------------------------------------------------------------

function SimpleVersium.new(self, params, versium)
   local dir = params.dir
   local obj = {dir=dir, node_table={}, versium=versium}
   setmetatable(obj, self)
   self.__index = self
   for x in lfs.dir(dir) do
      if x:len() > 2 then
	 obj.node_table[x] = 1
      end
   end
   return obj         
end


----------------------------------------------------------------------------
-- Returns a table representing the node with a given id.
--
-- @param version    the desired version of the node (defaults to current).
-- @return           the node as a string.
----------------------------------------------------------------------------

function SimpleVersium.get_node(self, id, version)
   local history = self:get_node_history(id)
   local node
   if version then
      node = history[#history-tonumber(version)+1]
   else
      node = history[1]
   end
   node.data = _read_file(self.dir.."/"..id.."/"..node.version)
   return node
end


----------------------------------------------------------------------------
-- Returns true or false depending on whether the node with this id
-- exists.
-- 
-- @param id         an id of an node.
-- @return           true or false.
----------------------------------------------------------------------------

function SimpleVersium.node_exists (self, id)
   return self.node_table[id]
end

----------------------------------------------------------------------------
-- Returns a table with the metadata for the latest version of the
-- node, same as get_history(id)[1] but potentially cheaper in case of
-- other implementations.
-- 
-- @param id         an id of an node.
-- @return           the metadata for the latest version or nil.
----------------------------------------------------------------------------

function SimpleVersium.get_node_info (self, id)
   return self:get_history(id)[1]
end


----------------------------------------------------------------------------
-- Returns a list of ids of all nodes in the repository, in no
-- particular order.
-- 
-- @return           a list of IDs
----------------------------------------------------------------------------

function SimpleVersium.get_node_ids (self)
   ids = {} 
   for id, _ in pairs(self.node_table) do
      ids[#ids+1] = id
   end
   return ids
end

----------------------------------------------------------------------------
-- Saves a new version of the node.
--
-- @param id         the id of the node.
-- @param data       the value to save.
-- @param author     the user name to be associated with the change.
-- @param comment    the change comment.
-- @param extra      any extra metadata
-- @return           the version of ID of the new node.
----------------------------------------------------------------------------

function SimpleVersium.save_version (self, id, data, author, comment, extra)
   local node_path = self.dir.."/"..id
   -- create a directory if necessary
   if not self:node_exists(id) then
      lfs.mkdir(node_path)
      self.node_table[id] = 1
   end

   local history, raw_history = self:get_node_history(id)
   local new_version_id = string.format("%06d", #history + 1)

   _write_file(node_path.."/"..new_version_id, data)

   local t = os.date("*t")
   local timestamp = string.format("%02d-%02d-%02d %02d:%02d:%02d", 
				   t.year, t.month, t.day,
				   t.hour, t.min, t.sec)
   local new_history = string.format(INDEX_TEMPLATE, 
				     self.versium:longquote(new_version_id),
				     self.versium:longquote(timestamp),
				     self.versium:longquote(author), 
				     self.versium:longquote(comment)) 
   -- ::TODO:: add support for saving "extra" parameters
   _write_file(self.dir.."/"..id.."/index", new_history..raw_history)
   return new_version_id
end


----------------------------------------------------------------------------
-- Returns the history of the node as a list of tables, each
-- representing a revision of the node (with fields like version,
-- author, comment, and extra).  The version can be filtered by a time
-- prefix.
--
-- @param id         the id of the node.
-- @param prefix     time prefix.
-- @return           a list of tables representing the versions and the 
--                   raw prepresentation of nodes history (as lua code).
----------------------------------------------------------------------------

function SimpleVersium.get_node_history (self, id, prefix)
   local raw_history = _read_file_if_exists(self.dir.."/"..id.."/index")
   local all_versions = {}
   local env = self.versium:load_lua(raw_history, 
				     { add_version = function (values)
					  table.insert(all_versions, values)
				       end } )
   -- ::TODO:: add support for prefix
   return all_versions, raw_history
end


----------------------------------------------------------------------------
-- Instantiate a new simple versium object.
-- 
-- @param impl       the implementation module.
-- @param params     the parameters to pass to the implementation.
-- @return           a new versium object.
----------------------------------------------------------------------------

function open(params, versium)
   return SimpleVersium:new(params, versium)
end
